package io.bio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Selector的作用是什么？
 *
 * 选择器提供选择执行已经就绪的任务的能力。从底层来看，Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是，只需要更少的线程来处理通道。事实上，可以只用一个线程处理所有的通道，这样会大量的减少线程之间上下文切换的开销。
 * 并不是所有的Channel，都是可以被Selector 复用的。比方说，FileChannel就不能被选择器复用。为什么呢？
 * 判断一个Channel 能被Selector 复用，有一个前提：判断他是否继承了一个抽象类SelectableChannel。如果继承了SelectableChannel，则可以被复用，否则不能。
 *
 * SelectableChannel类是何方神圣？
 *
 * SelectableChannel类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。所有socket通道，都继承了SelectableChannel类都是可选择的，包括从管道(Pipe)对象的中获得的通道。而FileChannel类，没有继承SelectableChannel，因此是不是可选通道。
 * 通道和选择器注册之后，他们是绑定的关系吗？
 * 答案: 不是。不是一对一的关系。一个通道可以被注册到多个选择器上，但对每个选择器而言只能被注册一次。
 * 通道和选择器之间的关系，使用注册的方式完成。SelectableChannel可以被注册到Selector对象上，在注册的时候，需要指定通道的哪些操作，是Selector感兴趣的。
 *
 * JDK的Selector在Linux等主流操作系统上通过epoll实现，它没有连接句柄数的限制（只受限于操作系统的最大句柄数或者对单个进程的句柄限制），
 * 这意味着一个Selector线程可以同时处理成千上万个客户端连接，而且性能不会随着客户端的增加而线性下降。因此，它非常适合做高性能、高负载的网络服务器。
 */
public class SelectorDemo {
    public static void main(String[] args) {
        try {
            // 获取selector 选择器
            Selector selector = Selector.open();
            // 获取通道
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(9999));
            // 将通道设置为非阻塞的
            ssc.configureBlocking(false);
            /**
             * 使用Channel.register（Selector sel，int ops）方法，将一个通道注册到一个选择器时。第一个参数，指定通道要注册的选择器是谁。第二个参数指定选择器需要查询的通道操作。
             * 可以供选择器查询的通道操作，从类型来分，包括以下四种：
             * （1）可读 : SelectionKey.OP_READ
             * （2）可写 : SelectionKey.OP_WRITE
             * （3）连接 : SelectionKey.OP_CONNECT
             * （4）接收 : SelectionKey.OP_ACCEPT
             * 如果Selector对通道的多操作类型感兴趣，可以用“位或”操作符来实现：int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;
             * 注意，操作一词，是一个是使用非常泛滥，也是一个容易混淆的词。特别提醒的是，选择器查询的不是通道的操作，而是通道的某个操作的一种就绪状态。
             * 什么是操作的就绪状态？
             * 一旦通道具备完成某个操作的条件，表示该通道的某个操作已经就绪，就可以被Selector查询到，程序可以对通道进行对应的操作。比方说，某个SocketChannel通道可以连接到一个服务器，则处于“连接就绪”(OP_CONNECT)。再比方说，一个ServerSocketChannel服务器通道准备好接收新进入的连接，则处于“接收就绪”（OP_ACCEPT）状态。还比方说，一个有数据可读的通道，可以说是“读就绪”(OP_READ)。一个等待写数据的通道可以说是“写就绪”(OP_WRITE)。
             *
             *
             * 与Selector一起使用时，Channel必须处于非阻塞模式下，否则将抛出异常IllegalBlockingModeException。
             * 这意味着，FileChannel不能与Selector一起使用，因为FileChannel不能切换到非阻塞模式，
             * 而套接字相关的所有的通道都可以。
             * 另外，还需要注意的是：
             * 一个通道，并没有一定要支持所有的四种操作。比如服务器通道ServerSocketChannel支持Accept 接受操作，
             * 而SocketChannel客户端通道则不支持。可以通过通道上的validOps()方法，来获取特定通道下所有支持的操作集合。
             */
            //将通道注册到选择器上,并制定selector对该通道感兴趣的操作是接收就绪
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            while(true){
                /**
                 * 万事俱备，可以开干。下一步是查询就绪的操作。
                 * 通过Selector的select（）方法，可以查询出已经就绪的通道操作，这些就绪的状态集合，包存在一个元素是SelectionKey对象的Set集合中。
                 * 下面是Selector几个重载的查询select()方法：
                 * （1）select():阻塞到至少有一个通道在你注册的事件上就绪了。
                 * （2）select(long timeout)：和select()一样，但最长阻塞事件为timeout毫秒。
                 * （3）selectNow():非阻塞，只要有通道就绪就立刻返回。
                 * select()方法返回的int值，表示有多少通道已经就绪，更准确的说，是自前一次select方法以来到这一次select方法之间的时间段上，有多少通道变成就绪状态。
                 * 一旦调用select()方法，并且返回值不为0时，下一步工干啥？
                 * 通过调用Selector的selectedKeys()方法来访问已选择键集合，然后迭代集合的每一个选择键元素，根据就绪操作的类型，完成对应的操作：
                 */
                if (selector.select()>0){
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while(iterator.hasNext()){
                        SelectionKey key = iterator.next();
                       if (key.isAcceptable()){

                       }else if(key.isReadable()){

                       }else if(key.isConnectable()){

                       }else if(key.isWritable()){

                       }
                       iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
